from fastapi import FastAPI, HTTPException, Response, Query
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from typing import List, Optional, AsyncGenerator
from src.agents.mrp_agent import run_mrp_agent, run_interactive_mrp_agent
import re
import json
from datetime import date
import urllib.parse
import asyncio

app = FastAPI(title="物流Agent API", 
              description="这是一个基于 LangChain 构建的物流领域 AI Agent API，专门用于处理生产计划与库存管理相关的任务。",
              version="1.0.0")

# 配置CORS
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # 在生产环境中应该指定具体的域名
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 定义请求数据模型，用于接收MRP计算所需的参数
class MRPRequest(BaseModel):
    # 可选的总需求量参数
    total_demand: Optional[float] = None
    # 可选的日需求量参数
    daily_demand: Optional[float] = None
    # 可选的订货批量参数
    order_qty: Optional[float] = None
    # 可选的订货点参数
    reorder_point: Optional[float] = None
    # 可选的订货提前期参数（天数）
    lead_time_days: Optional[int] = None
    # 可选的生产开始日期参数
    start_date: Optional[date] = None
    # 可选的初始库存参数
    initial_stock: Optional[float] = None
    # 可选的查询字符串参数
    query: Optional[str] = None

# 定义订单信息数据模型，用于表示单个订单的信息
class OrderInfo(BaseModel):
    # 订单序号
    序号: int
    # 下单日期
    order_date: str
    # 到货日期
    arrival_date: str
    # 到货后的库存量
    stock_after_arrival: float

# 定义每日库存计划数据模型，用于表示每日的库存情况
class DailyStockPlan(BaseModel):
    # 日期
    date: str
    # 当天开始时的库存量
    start_stock: float
    # 当天发生的事件描述
    events: str

# 定义MRP响应数据模型，用于封装MRP计算的完整结果
class MRPResponse(BaseModel):
    # 订单信息列表
    orders_info: List[OrderInfo]
    # 每日库存计划列表
    daily_stock_plan: List[DailyStockPlan]

@app.get("/calculate_mrp_stream", 
         summary="流式计算MRP计划",
         description="以流式方式计算物料需求计划，包括订单与到货信息表以及每日库存计划")
# 定义流式计算MRP计划的GET接口，支持多种查询参数
async def calculate_mrp_stream_get(
    # 查询字符串参数，用于传递自然语言指令
    query: str = Query(None, description="查询字符串"),
    # 总需求量参数
    total_demand: float = Query(None, description="总需求量"),
    # 日需求量参数
    daily_demand: float = Query(None, description="日需求量"),
    # 订货批量参数
    order_qty: float = Query(None, description="订货批量"),
    # 订货点参数
    reorder_point: float = Query(None, description="订货点"),
    # 订货提前期参数
    lead_time_days: int = Query(None, description="订货提前期"),
    # 生产开始日期参数
    start_date: date = Query(None, description="生产开始日期"),
    # 初始库存参数
    initial_stock: float = Query(None, description="初始库存")
):
    """
    流式计算MRP计划的入口点
    
    参数:
    - query: 自然语言查询指令
    - total_demand: 总需求量
    - daily_demand: 日需求量
    - order_qty: 订货批量
    - reorder_point: 订货点
    - lead_time_days: 订货提前期（天）
    - start_date: 生产开始日期
    - initial_stock: 初始库存
    
    返回:
    - 流式响应，包含计算过程和最终结果
    """
    # 构造请求对象，将查询参数封装到MRPRequest模型中
    request = MRPRequest(
        query=query,
        total_demand=total_demand,
        daily_demand=daily_demand,
        order_qty=order_qty,
        reorder_point=reorder_point,
        lead_time_days=lead_time_days,
        start_date=start_date,
        initial_stock=initial_stock
    )
    # 调用核心逻辑函数处理请求
    return await calculate_mrp_stream_logic(request)

async def calculate_mrp_stream_logic(request: MRPRequest):
    """
    MRP流式计算的核心逻辑函数
    
    参数:
    - request: 包含计算所需参数的MRPRequest对象
    
    返回:
    - StreamingResponse对象，支持服务器发送事件(SSE)
    """
    # 构造查询字符串，如果提供了query参数则使用它，否则构造一个包含所有参数的详细查询
    query = request.query or f"""
    计算MRP计划
    总需求量: {request.total_demand if request.total_demand is not None else '未提供'}
    日需求量: {request.daily_demand if request.daily_demand is not None else '未提供'}
    订货批量: {request.order_qty if request.order_qty is not None else '未提供'}
    订货点: {request.reorder_point if request.reorder_point is not None else '未提供'}
    订货提前期: {request.lead_time_days if request.lead_time_days is not None else '未提供'}
    生产开始日期: {request.start_date if request.start_date is not None else '未提供'}
    初始库存: {request.initial_stock if request.initial_stock is not None else '未提供'}
    """
    
    # 定义事件生成器函数，用于产生流式响应数据
    async def event_generator():
        """
        事件生成器，负责产生流式响应数据
        
        产生三种类型的数据:
        - chunk: 计算过程中的文本片段
        - result: 最终计算结果
        - error: 错误信息
        - end: 结束信号
        """
        # 存储完整的计算结果文本
        result_text = ""
        # 流式获取MRP计算结果，逐块处理AI Agent返回的内容
        for chunk in run_mrp_agent(query):
            result_text += chunk
            # 逐字符发送，实现打字机效果，增强用户体验
            for char in chunk:
                # 将每个字符包装成JSON格式并通过SSE协议发送
                yield f"data: {json.dumps({'type': 'chunk', 'content': char})}\n\n"
                # 等待一小段时间，控制打字机效果的速度
                await asyncio.sleep(0.01)
        
        try:
            # 解析最终结果，提取订单信息表部分
            orders_start = result_text.find("订单与到货信息表:")
            # 提取每日库存计划部分
            stock_start = result_text.find("每日库存计划:")
            
            # 检查是否找到了必需的部分
            if orders_start == -1 or stock_start == -1:
                raise ValueError("无法解析MRP计算结果")
            
            # 分割结果文本为订单信息和库存计划两部分
            orders_section = result_text[orders_start:stock_start]
            stock_section = result_text[stock_start:]
            
            # 解析订单信息表
            orders_lines = orders_section.strip().split('\n')
            orders_info = []
            
            # 从第4行开始处理（跳过标题和表头行）
            for line in orders_lines[3:]:
                # 检查是否是有效的表格行
                if '|' in line and line.count('|') >= 4:
                    # 分割行内容并去除空格
                    parts = [p.strip() for p in line.split('|') if p.strip()]
                    # 确保有足够的列
                    if len(parts) >= 4:
                        try:
                            # 创建订单信息对象并添加到列表中
                            order_info = OrderInfo(
                                序号=int(parts[0]),
                                order_date=parts[1],
                                arrival_date=parts[2],
                                stock_after_arrival=float(parts[3].replace('吨', ''))
                            )
                            orders_info.append(order_info)
                        except ValueError:
                            # 如果转换失败则跳过该行
                            continue
            
            # 解析每日库存计划
            stock_lines = stock_section.strip().split('\n')
            daily_stock_plan = []
            
            # 从第3行开始处理（跳过标题和表头行）
            for line in stock_lines[2:]:
                # 检查是否是有效的表格行
                if '|' in line and line.count('|') >= 3:
                    # 分割行内容并去除空格
                    parts = [p.strip() for p in line.split('|') if p.strip()]
                    # 确保有足够的列
                    if len(parts) >= 3:
                        try:
                            # 创建库存计划对象并添加到列表中
                            stock_info = DailyStockPlan(
                                date=parts[0],
                                start_stock=float(parts[1].replace('吨', '')),
                                events=parts[2]
                            )
                            daily_stock_plan.append(stock_info)
                        except ValueError:
                            # 如果转换失败则跳过该行
                            continue
            
            # 构造最终结果对象
            final_result = MRPResponse(
                orders_info=orders_info,
                daily_stock_plan=daily_stock_plan
            )
            # 发送最终结果
            yield f"data: {json.dumps({'type': 'result', 'content': final_result.dict()})}\n\n"
        except Exception as e:
            # 发送错误信息
            yield f"data: {json.dumps({'type': 'error', 'content': f'解析MRP结果失败: {str(e)}'})}\n\n"
        finally:
            # 发送结束信号，通知客户端数据传输完成
            yield "data: {\"type\": \"end\"}\n\n"
    
    # 返回流式响应对象，使用text/event-stream媒体类型
    return StreamingResponse(event_generator(), media_type="text/event-stream")